﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Windows.Forms;
namespace IndustryFieldCommunication
{
    using MsgFuncsMap = Dictionary<string, Action<MsgProcess, string>>;

    class MsgProcess
    {
        public MsgProcess(
            Socket client,
            Action<string> InProcess,
            Action<string> Canceled,
            Action<string[]> Monitored,
            Action<string> Done,
            Action<bool> CheckHeartBeat,
            ref EventHandler Disposing)
        {
            Client = client;
            this.InProcess = InProcess;
            this.Canceled = Canceled;
            this.Monitored = Monitored;
            this.Done = Done;
            this.CheckHeartBeat = CheckHeartBeat;
            Disposing += (o, e) => StopReceive();
            StartReceive();
        }

        public void StartReceive()
        {
            ThreadPool.QueueUserWorkItem((o) =>
                Client.BeginReceive(Buffer, 0, BufferSize, 0, ReceiveCallBack, this));
            //Task.Run(() =>
            //Client.BeginReceive(Buffer, 0, BufferSize, 0, ReceiveCallBack, this))
        }

        public void StopReceive()
        {
            try
            {
                if (Client.Connected)
                {
                    Client.Shutdown(SocketShutdown.Both);
                }
            }
            catch (SocketException e)
            {
                /*
                // 10035 == WSAEWOULDBLOCK
                //If the error returned WSAEWOULDBLOCK, the remote host connection
                //has been initiated by a connection-oriented Socket,
                //but has not yet completed successfully.
                if (e.NativeErrorCode.Equals(10035))
                    Client.Shutdown(SocketShutdown.Both);
                else*/
                MessageBox.Show(e.ToString(), "StopReceive1", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
            catch (ObjectDisposedException e)
            {
                MessageBox.Show(e.ToString(), "StopReceive2", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }

        private static void ReceiveCallBack(IAsyncResult ar)
        {
            var msgProcesser = (MsgProcess)ar.AsyncState;
            try
            {
                int bytesRead = msgProcesser.Client.EndReceive(ar);
                if (bytesRead == 0)
                {
                    msgProcesser.Client.Shutdown(SocketShutdown.Both);
                    msgProcesser.Client.Close();
                }
                else
                {
                    var buf = msgProcesser.Buffer;
                    var rawBytes = msgProcesser.RawBytes;
                    for (int i = 0; i < bytesRead; ++i)
                    {
                        rawBytes.Add(buf[i]);
                    }
                    while (true)
                    {
                        if (rawBytes.Count >= 4)
                        {
                            var data = rawBytes.ToArray();
                            int bodySize = BitConverter.ToInt32(data, 0);
                            int msgLength = 4 + bodySize;
                            if (rawBytes.Count >= msgLength)
                            {
                                var msg = Encoding.ASCII.GetString(
                                    data, 4, bodySize);
                                msgProcesser.ProcessMsg(msg);
                                rawBytes.RemoveRange(0, msgLength);
                            }
                            else
                                break;
                        }
                        else
                            break;
                    }
                    msgProcesser.StartReceive();
                }
            }
            catch (ObjectDisposedException e)
            {
                MessageBox.Show(e.ToString(), "ReceiveCallBack1", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
            catch (Exception e)
            {
                MessageBox.Show(e.ToString(), "ReceiveCallBack2", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                msgProcesser.Client.Close();
            }
        }

        private void ProcessMsg(string msg)
        {
            char[] delimiters = { ',', ';' };
            var nameIdx = msg.IndexOfAny(delimiters);
            var name = msg.Substring(0, nameIdx);
            MsgFuncs[name]?.Invoke(this, msg.Substring(nameIdx + 1));

        }

        static MsgFuncsMap MsgFuncs = new()
        {

            ["Palletize"] = (MsgProcesser, Params) =>
            {
                char[] delimiters = { ',', ';' };
                var data = Params.Split(delimiters);
                MsgProcesser.InProcess?.Invoke(data[0]);
            },
            ["Cancel"] = (MsgProcesser, Params) =>
            {
                var task = MsgProcesser.Tasks[MsgProcesser.CurrentTaskIndex];
                if (task.State != TaskState.Done && Params.Contains(task.TaskID))
                {
                    task.State = TaskState.Canceled;
                    MsgProcesser.Canceled?.Invoke(task.TaskID);
                }
                MsgProcesser.GetIfReady();
            },
            ["Monitor"] = (MsgProcesser, Params) =>
            {
                MsgProcesser.Responed = true;
                char[] delimiters = { ',', ';' };
                var data = Params.Split(delimiters);
                MsgProcesser.Monitored?.Invoke(data);
            },
            ["Done"] = (MsgProcesser, Params) =>
            {
                var task = MsgProcesser.Tasks[MsgProcesser.CurrentTaskIndex];
                task.State = TaskState.Done;
                MsgProcesser.Done?.Invoke(task.TaskID);
                MsgProcesser.GetIfReady();
            },
            ["ReadyForTask"] = (MsgProcesser, Params) =>
            {
                if (Params == "true;")
                {
                    MsgProcesser.SendTask();
                    //MsgProcesser.StopQuery();
                }
                else
                {
                    //MsgProcesser.PeriodicallyQuery();
                }

            }
        };

        private void GetIfReady()
        {
            SendMessage("ReadyForTask;");
        }

        //private void PeriodicallyQuery()

        private void SendTask()
        {
            while (CurrentTaskIndex < Tasks.Count)
            {
                var task = Tasks[CurrentTaskIndex];
                lock (task)
                {
                    if (task.State == TaskState.InProcess)
                        break; 
                    if (task.State == TaskState.Pending)
                    {
                        var msg = task.Name + "," + task.TaskID + "," + task.Params;
                        SendMessage(msg);
                        task.State = TaskState.InProcess;
                        break;
                    }
                    else
                        ++CurrentTaskIndex;
                }
            }
        }

        private void SendMessage(string msgBody)
        {
            var body = Encoding.ASCII.GetBytes(msgBody);
            var header = BitConverter.GetBytes(body.Length);
            var msg = header.Concat(body).ToArray();
            try
            {
                Client.SendAsync(msg, 0);
            }
            catch(ObjectDisposedException e)
            {
                MessageBox.Show("Socket已销毁\n" + e, "SendMessage", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }

        //UI可调用的方法，以下
        public string CreateTask(int lenth, int width, int height)
        {
            if (lenth <= 6 && width <= 2 && height <= 3 &&
                0 < lenth && 0 < width && 0 < height &&
                lenth * width * height <= 12)
            {
                var Params = lenth.ToString() + "," + width.ToString() + "," + height.ToString() + ";";
                var task = new TaskObj("Palletize", TaskID.ToString(), Params, TaskState.Pending);
                TaskID++;
                Tasks.Add(task);
                TaskIDMap.Add(task.TaskID, task);
                GetIfReady();//可以查看本地记录（CurrentTaskIndex）而不是访问远程
                return task.TaskID;
            }
            else
                return "无效参数。0<长<=6，0<宽<=2，0<高<=3，总共<=12。";
        }

        public void CancelTask(string taskID)
        {
            var task = TaskIDMap[taskID];
            lock (task)
            {
                if (task.State != TaskState.Done)
                {
                    if (task.State == TaskState.InProcess)
                    {
                        SendMessage($"Cancel,{task.TaskID};");
                    }
                    else
                    {
                        task.State = TaskState.Canceled;
                        Canceled?.Invoke(taskID);
                    }
                }
            }
        }

        public void StartMonitor(double ms)
        {
            SendMessage("Monitor;");
            if (ms < 100)
                ms = 100;
            MonitorTrigger = new System.Timers.Timer(ms);
            MonitorTrigger.Elapsed += (o, e) =>
            {
                if (Client.Connected)
                {
                    SendMessage("Monitor;");
                    CheckHeartBeat?.Invoke(Responed);
                    Responed = false;
                }
                else
                    StopMonitor();
            };
            MonitorTrigger.AutoReset = true;
            MonitorTrigger.Enabled = true;
        }

        public void StopMonitor()
        {
            MonitorTrigger.Stop();
            MonitorTrigger.Dispose();
        }

        //任务数据
        public List<TaskObj> Tasks { get; } = new();
        public Dictionary<string, TaskObj> TaskIDMap { get; } = new();
        int CurrentTaskIndex;
        int TaskID;
        //委托
        Action<string> InProcess;
        Action<string> Canceled;
        Action<string[]> Monitored;
        Action<string> Done;
        Action<bool> CheckHeartBeat;
        //消息接收
        const int BufferSize = 1024;
        byte[] Buffer = new byte[BufferSize];
        List<byte> RawBytes = new(BufferSize * 2);
        Socket Client;
        bool Responed;
        //周期触发事件
        System.Timers.Timer MonitorTrigger;

        public enum TaskState
        {
            Pending,
            InProcess,
            Done,
            Canceled
        }
        public class TaskObj
        {
            public string Name;
            public string TaskID;
            public string Params;
            public TaskState State;

            public TaskObj(string N, string T, string P, TaskState S)
            {
                Name = N;
                TaskID = T;
                Params = P;
                State = S;
            }
        }
    }
}
